#include "PageHandler.h"
#include "include/wrapper/cef_helpers.h"
#include "resource.h"
#include "include/views/cef_browser_view.h"
#include "include/views/cef_window.h"
#include "nlohmann/json.hpp"
#include <codecvt>
#include "DialogHandler.h"
#include "include/wrapper/cef_resource_manager.h"
#include <iostream>
#include <fstream>
#include <fileapi.h>
#include <shtypes.h>
#include <ShlObj_core.h>
#include <filesystem>

static const std::wstring dbName = L"cefl.db";
static sqlite3* sqlite;

namespace
{
	LPCWSTR infoValue;
	LPWSTR userInputValue;
	LPCWSTR defaultValue;
	BOOL CALLBACK PromptProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam)
	{
		BOOL result = FALSE;
		switch (message)
		{
		case WM_INITDIALOG:
		{
			SetDlgItemText(hwndDlg, IDC_Label, infoValue);
			SetDlgItemText(hwndDlg, IDC_INPUT, defaultValue);
			HWND hwndInput = GetDlgItem(hwndDlg, IDC_INPUT);
			SetFocus(hwndInput);
			SendMessage(hwndInput, EM_SETSEL, 0, -1);
			break;
		}
		case WM_COMMAND:
		{
			switch (LOWORD(wParam))
			{
			case IDOK: 
			{
				HWND hwndInput = GetDlgItem(hwndDlg, IDC_INPUT);
				int outLength = GetWindowTextLength(hwndInput) + 1;
				userInputValue = new WCHAR[outLength];
				GetDlgItemText(hwndDlg, IDC_INPUT, userInputValue, outLength);
				EndDialog(hwndDlg, wParam);
				result = TRUE;
				break;
			}
			case IDCANCEL:
			{
				EndDialog(hwndDlg, wParam);
				result = FALSE;
				break;
			}
			}
			break;
		}
		default:
			break;
		}
	return result;
	}

	std::vector<std::string> split(const std::string& s, char delim)
	{
		std::vector<std::string> elems;
		std::stringstream ss(s);
		std::string item;
		while (std::getline(ss, item, delim))
		{
			elems.push_back(item);
		}
		return elems;
	}

	const std::string getOSVersion()
	{
		std::string result = "10.0.0.1:2663";
		/*NTSTATUS(WINAPI * RtlGetVersion)(LPOSVERSIONINFOEXW);
		OSVERSIONINFOEXW osInfo;
		std::string result;
		*(FARPROC*)&RtlGetVersion = GetProcAddress(GetModuleHandle(L"ntdll"), "RtlGetVersion");
		if (nullptr != RtlGetVersion) {
			osInfo.dwOSVersionInfoSize = sizeof osInfo;
			RtlGetVersion(&osInfo);
			result = std::to_string(osInfo.dwMajorVersion) + "." + std::to_string(osInfo.dwMinorVersion) + "." + std::to_string(osInfo.dwBuildNumber);
		}*/
		return result;
	}

	std::wstring convertStr(const std::string& str)
	{
		static std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8_convert;
		return utf8_convert.from_bytes(str);
	}

	bool FileExist(const std::wstring& filePath)
	{
		std::error_code ec;
		bool exists = std::filesystem::exists(std::filesystem::path(filePath), ec);
		if (ec || ec == std::errc::no_such_file_or_directory)
		{
			return false;
		}
		return exists;
	}

	bool MakePath(const std::wstring& path)
	{
		bool ret = FileExist(path);
		if (ret)
		{
			return ret;
		}
		bool mkRet = std::filesystem::create_directories(path);
		return mkRet;
	}

	std::tuple<bool, std::wstring> CreateDBDirectory()
	{
		wchar_t defaultDir[MAX_PATH]{ 0 };
		wchar_t szPath[MAX_PATH]{ 0 };
		SecureZeroMemory(defaultDir, MAX_PATH);

		LPITEMIDLIST pidl = NULL;
		HRESULT ret = SHGetSpecialFolderLocation(NULL, CSIDL_COMMON_APPDATA, &pidl);
		if (pidl && SHGetPathFromIDList(pidl, szPath))
		{
			GetLongPathName(szPath, defaultDir, _MAX_PATH);
		}
		std::wstring directory = std::wstring(szPath);
		directory += L"\\CEFLearn";

		bool mkret = MakePath(directory);
		if (mkret)
		{
			std::tuple<bool, std::wstring> t = std::make_tuple(mkret, directory);
			return t;
		}
		return std::tuple<bool, std::wstring>();
	}
	void ExecuteSQL(const std::string& sqlStr, const std::string& msgName, CefRefPtr<CefFrame> frame)
	{
		nlohmann::json result;
		result["data"] = {};
		const char* zTail = sqlStr.c_str();
		sqlite3_exec(sqlite, "BEGIN TRANSACTION;", NULL, NULL, NULL);
		while (strlen(zTail) != 0)
		{
			sqlite3_stmt* stmt = NULL;
			int prepareResult = sqlite3_prepare_v2(sqlite, zTail, -1, &stmt, &zTail);
			int stepResult = sqlite3_step(stmt);
			while (stepResult == SQLITE_ROW)
			{
				nlohmann::json row;
				int columnCount = sqlite3_column_count(stmt);
				for (int i = 0; i < columnCount; i++)
				{
					const char* columnName = sqlite3_column_name(stmt, i);
					int type = sqlite3_column_type(stmt, i);
					if (type == SQLITE_INTEGER)
					{
						row[columnName] = sqlite3_column_int(stmt, i);
					}
					else if (type == SQLITE3_TEXT) 
					{
						const unsigned char* columnValue = sqlite3_column_text(stmt, i);
						if (columnValue)
						{
							row[columnName] = reinterpret_cast<const char*>(columnValue);
						}
					}
				}
				result["data"].push_back(row);
				stepResult = sqlite3_step(stmt);
			}
			sqlite3_finalize(stmt);
		}
		sqlite3_exec(sqlite, "END TRANSACTION;", NULL, NULL, NULL);
		CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create(msgName);
		CefRefPtr<CefListValue> msgArgs = msg->GetArgumentList();
		result["success"] = true;
		msgArgs->SetString(0, result.dump());
		frame->SendProcessMessage(PID_RENDERER, msg);
	}
}

void ReadFileByBlocks(const std::string& filePath,
	const std::string& msgName,
	CefRefPtr<CefFrame> frame)
{
	std::ifstream fileStream(filePath, std::ifstream::binary);
	if (fileStream) {
		fileStream.seekg(0, fileStream.end);
		long length = fileStream.tellg();
		fileStream.seekg(0, fileStream.beg);
		long size = 1024;
		long position = 0;
		while (position != length) {
			long leftSize = length - position;
			if (leftSize < size) {
				size = leftSize;
			}
			char* buffer = new char[size];
			fileStream.read(buffer, size);
			position = fileStream.tellg();
			CefRefPtr<CefBinaryValue> data = CefBinaryValue::Create(buffer, size);
			CefRefPtr<CefProcessMessage> msgBack = CefProcessMessage::Create(msgName + "_data");
			CefRefPtr<CefListValue> msgArgs = msgBack->GetArgumentList();
			msgArgs->SetBinary(0, data);
			frame->SendProcessMessage(PID_RENDERER, msgBack);
			delete[] buffer;
		}
		fileStream.close();
		CefRefPtr<CefProcessMessage> msgBack = CefProcessMessage::Create(msgName + "_finish");
		frame->SendProcessMessage(PID_RENDERER, msgBack);
	}
}

void PageHandler::OnAfterCreated(CefRefPtr<CefBrowser> browser)
{
	CEF_REQUIRE_UI_THREAD();
	m_browsers.push_back(browser);
}

void PageHandler::OnBeforeClose(CefRefPtr<CefBrowser> browser)
{
	CEF_REQUIRE_UI_THREAD();
	std::list<CefRefPtr<CefBrowser>>::iterator bit = m_browsers.begin();
	for (; bit != m_browsers.end(); ++bit)
	{
		if ((*bit)->IsSame(browser)) {
			m_browsers.erase(bit);
			break;
		}
	}

	if (m_browsers.empty())
	{
		CefQuitMessageLoop();
	}
}

bool PageHandler::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser, 
	CefRefPtr<CefFrame> frame, 
	CefProcessId source_process, CefRefPtr<CefProcessMessage> message)
{
	CEF_REQUIRE_UI_THREAD();
	std::string message_name = message->GetName();
	std::vector<std::string> arr = split(message_name, '_');
	if (arr.at(0) == "window")
	{
		CefRefPtr<CefBrowserView> browser_view = CefBrowserView::GetForBrowser(browser);
		CefRefPtr<CefWindow> window = browser_view->GetWindow();
		if (arr.at(1) == "minimize")
		{
			window->Minimize();
		}
		else if (arr.at(1) == "maximize") {
			window->Maximize();
		}
		else if (arr.at(1) == "close") {
			window->Close();
		}
		else if (arr.at(1) == "restore") {
			window->Restore();
		}
	}
	else if (arr.at(0) == "system") {
		std::string messageName = message->GetName();
		CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create(messageName);
		CefRefPtr<CefListValue> msgArgs = msg->GetArgumentList();
		if (arr.at(1) == "getOSVersion")
		{
			std::string version = getOSVersion();
			msgArgs->SetString(0, version);
		}
		frame->SendProcessMessage(PID_RENDERER, msg);
	}
	else if (arr.at(0) == "dialog")
	{
		std::string messageName = message->GetName();
		CefRefPtr<CefListValue> msgBody = message->GetArgumentList();
		nlohmann::json param = nlohmann::json::parse(msgBody->GetString(0).ToString());
		std::wstring title = convertStr(param["title"].get<std::string>());
		std::wstring defaultPath = convertStr(param["defaultPath"].get<std::string>());
		CefRefPtr<CefRunFileDialogCallback> dcb = new DialogHandler(messageName, frame);
		CefBrowserHost::FileDialogMode mode;
		std::vector<CefString> fileFilters;
		int filterIndex = 0;
		if (arr.at(1) == "openFile")
		{
			for (const std::string& var: param["filters"])
			{
				fileFilters.push_back(var);
			}
			filterIndex = param["filterIndex"].get<int>();
			mode = param["multiSelections"].get<bool>() ? FILE_DIALOG_OPEN_MULTIPLE : FILE_DIALOG_OPEN;
			browser->GetHost()->RunFileDialog(mode, title, defaultPath, fileFilters, dcb);
		}
		else if (arr.at(1) == "openFolder")
		{
			mode = FILE_DIALOG_OPEN_FOLDER;
			browser->GetHost()->RunFileDialog(mode, title, defaultPath, fileFilters, dcb);
		}
	}
	else if (arr.at(0) == "file")
	{
		if (arr.at(1) == "readFile")
		{
			if (arr.at(1) == "readFile")
			{
				std::string messageName = message->GetName();
				CefRefPtr<CefListValue> msgBody = message->GetArgumentList();
				nlohmann::json param = nlohmann::json::parse(msgBody->GetString(0).ToString());
				std::string filePath = param["filePath"].get<std::string>();
				CefPostTask(TID_FILE_BACKGROUND, base::BindOnce(&ReadFileByBlocks, filePath, messageName, frame));
			}
		}
	}
	else if (arr.at(0) == "db")
	{
		CefRefPtr<CefListValue> msgBody = message->GetArgumentList();
		if (arr.at(1) == "create")
		{
			auto mkret = CreateDBDirectory();
			bool res = std::get<0>(mkret);
			std::wstring directory = std::get<1>(mkret);
			if (res && directory.length() > 0 && sqlite == nullptr)
			{
				std::wstring dbPath = directory + L"\\" + dbName;
				int ret = sqlite3_open16(dbPath.c_str(), &sqlite);
				if (ret != SQLITE_OK)
				{
					sqlite3_close(sqlite);
					sqlite = nullptr;
				}
			}
		}
		else if (arr.at(1) == "open")
		{
			nlohmann::json param = nlohmann::json::parse(msgBody->GetString(0).ToString());
			auto result = CreateDBDirectory();
			bool ret = std::get<0>(result);
			std::wstring directory = std::get<1>(result);
			if (ret && directory.length() > 0 && sqlite == nullptr)
			{
				std::wstring dbPath = directory + L"\\" + dbName;
				int rc = sqlite3_open16(dbPath.c_str(), &sqlite);
				if (rc != SQLITE_OK)
				{
					sqlite3_close(sqlite);
					sqlite = nullptr;
				}
				CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create(message_name);
				CefRefPtr<CefListValue> msgArgs = msg->GetArgumentList();
				nlohmann::json result;
				result["success"] = rc == 0;
				msgArgs->SetString(0, result.dump());
				frame->SendProcessMessage(PID_RENDERER, msg);
			}
		}
		else if (arr.at(1) == "close")
		{
			int rc = sqlite3_close(sqlite);
			CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create(message_name);
			CefRefPtr<CefListValue> msgArgs = msg->GetArgumentList();
			nlohmann::json result;
			result["success"] = rc == 0;
			msgArgs->SetString(0, result.dump());
			frame->SendProcessMessage(PID_RENDERER, msg);
		}
		else if (arr.at(1) == "execute")
		{
			nlohmann::json param = nlohmann::json::parse(msgBody->GetString(0).ToString());
			std::string sql = param["sql"].get<std::string>();
			CefPostTask(TID_FILE_BACKGROUND, base::BindOnce(&ExecuteSQL, sql, message_name, frame));
		}
	}

	return true;
}

bool PageHandler::OnJSDialog(CefRefPtr<CefBrowser> browser,
	const CefString& origin_url, 
	JSDialogType dialog_type, 
	const CefString& message_text, 
	const CefString& default_prompt_text, 
	CefRefPtr<CefJSDialogCallback> callback, bool& suppress_message)
{
	suppress_message = false;
	CefWindowHandle mWnd = browser->GetHost()->GetWindowHandle();
	if (!mWnd)
	{
		return false;
	}
	if (dialog_type == JSDIALOGTYPE_ALERT)
	{
        MessageBox(mWnd, message_text.ToWString().c_str(), L"system hint", MB_ICONINFORMATION | MB_OK);
		callback->Continue(true, CefString());
	}
	else if (dialog_type == JSDIALOGTYPE_CONFIRM)
	{
		int msgId = ::MessageBoxW(mWnd, message_text.ToWString().c_str(), L"system hint", MB_ICONEXCLAMATION | MB_OKCANCEL);
		if (msgId == IDOK)
		{
			callback->Continue(true, CefString());
		}
		else {
			callback->Continue(false, CefString());
		}
	}
	else if (dialog_type == JSDIALOGTYPE_PROMPT)
	{
		HINSTANCE hInstance = GetModuleHandle(NULL);
		std::wstring promptText = default_prompt_text.ToWString();
		defaultValue = promptText.c_str();
		std::wstring msgText = message_text.ToWString();
		infoValue = msgText.c_str();
		BOOL result = DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), mWnd, (DLGPROC)PromptProc);
		if (result == IDOK)
		{
			callback->Continue(true, CefString(userInputValue));
			delete[] userInputValue;
		}
		else {
			callback->Continue(false, CefString());
		}
	}

	return true;
}

bool PageHandler::OnBeforeUnloadDialog(CefRefPtr<CefBrowser> browser, 
	const CefString& message_text, 
	bool is_reload, 
	CefRefPtr<CefJSDialogCallback> callback)
{
	CefWindowHandle mWnd = browser->GetHost()->GetWindowHandle();
	if (!mWnd)
	{
		return false;
	}
	int msgId = ::MessageBoxW(mWnd, L"content not save, confirm to leave?", L"system hint", MB_ICONEXCLAMATION | MB_OKCANCEL);
	if (msgId == IDOK)
	{
		callback->Continue(true, CefString());
	}
	else {
		callback->Continue(false, CefString());
	}
	return true;
}

void PageHandler::OnBeforeContextMenu(CefRefPtr<CefBrowser> browser, 
	CefRefPtr<CefFrame> frame, 
	CefRefPtr<CefContextMenuParams> params, 
	CefRefPtr<CefMenuModel> model)
{
	model->Clear();
	model->AddItem(MENU_ID_USER_FIRST, L"打开开发者调试工具");
	CefRefPtr<CefMenuModel> subModel = model->AddSubMenu(MENU_ID_USER_FIRST + 1, L"子菜单");
	subModel->AddItem(MENU_ID_USER_FIRST + 2, L"子菜单1");
	subModel->AddItem(MENU_ID_USER_FIRST + 3, L"子菜单2");
	model->AddSeparator();
	model->AddCheckItem(MENU_ID_USER_FIRST + 4, L"这是一个包含复选框的菜单");
	model->SetChecked(MENU_ID_USER_FIRST + 4, true);

}

bool PageHandler::OnContextMenuCommand(CefRefPtr<CefBrowser> browser, 
	CefRefPtr<CefFrame> frame, 
	CefRefPtr<CefContextMenuParams> params, 
	int command_id, EventFlags event_flags)
{
	switch (command_id)
	{
	case MENU_ID_USER_FIRST:
	{
		CefBrowserSettings browserSettings;
		CefWindowInfo windowInfo;
		CefPoint mousePoint(params->GetXCoord(), params->GetYCoord());
		browser->GetHost()->ShowDevTools(windowInfo, this, browserSettings, mousePoint);
		break;
	}
	default:
		std::wstring msg = L"你选择了菜单项: " + std::to_wstring(command_id);
		MessageBox(NULL, msg.c_str(), L"system hint", MB_OK);
		break;
	}
	return true;
}

void PageHandler::OnDraggableRegionsChanged(CefRefPtr<CefBrowser> browser, 
	CefRefPtr<CefFrame> frame, const std::vector<CefDraggableRegion>& regions)
{
	CefRefPtr<CefBrowserView> browser_view = CefBrowserView::GetForBrowser(browser);
	if (browser_view)
	{
		CefRefPtr<CefWindow> window = browser_view->GetWindow();
		if (window)
		{
			window->SetDraggableRegions(regions);
		}
	}
}

//void PageHandler::ReadFileByBlocks(const std::string& filePath, const std::string& msgName, CefRefPtr<CefFrame> frame)
//{
//	std::ifstream fileStream(filePath, std::ifstream::binary);
//	if (fileStream)
//	{
//		fileStream.seekg(0, fileStream.end);
//		long length = fileStream.tellg();
//		fileStream.seekg(0, fileStream.beg);
//		long size = 1024;
//		long position = 0;
//		while (position != length) {
//			long leftSize = length - position;
//			if (leftSize < size)
//			{
//				size = leftSize;
//			}
//			char* buffer = new char[size];
//			fileStream.read(buffer, size);
//			position = fileStream.tellg();
//			CefRefPtr<CefBinaryValue> data = CefBinaryValue::Create(buffer, size);
//			CefRefPtr<CefProcessMessage> msgBack = CefProcessMessage::Create(msgName + "_data");
//			CefRefPtr<CefListValue> msgArgs = msgBack->GetArgumentList();
//			msgArgs->SetBinary(0, data);
//			frame->SendProcessMessage(PID_RENDERER, msgBack);
//			delete[] buffer;
//		}
//		fileStream.close();
//		CefRefPtr<CefProcessMessage> msgBack = CefProcessMessage::Create(msgName + "_finish");
//		frame->SendProcessMessage(PID_RENDERER, msgBack);
//	}
//}
